; *****************************************************************************
; Данный пример является лёгкой переработкой примера из MSDN.
; Он показывает каким образом можно создать окно сообщения (MessageBox), который автоматически
; закрывается по истечении заданного интервала времени
; В примере используется таймер, который генерирует событие по истечении заданного интервала времени
; При наступлении события, функция PostQuitMessage используется для принудительного прерывания 
; цикла обработки сообщений окна сообщения.
; Заметьте, что сообщение WM_QUIT должно быть удалено из очереди сообщений, 
; чтобы его не получил основной цикл обработки сообщений.
; *****************************************************************************
.386
.model flat, stdcall
option casemap :none

	include	\masm32\include\windows.inc
	include \masm32\include\kernel32.inc
	include \masm32\include\user32.inc

	includelib \masm32\lib\kernel32.lib
	includelib \masm32\lib\user32.lib

	WinMain		proto :DWORD, :DWORD, :DWORD, :DWORD
	TimedMessageBox proto :DWORD, :DWORD, :DWORD, :DWORD, :DWORD
	MessageBoxTimer proto :DWORD, :DWORD, :DWORD, :DWORD
	EnumWindowsProc proto :DWORD, :DWORD
	UpdateCaption	proto :DWORD, :DWORD

.data
	dwTimeOut	dd 5000
	szQuestion	db "Правда ли, что у треугольника три угла?", 0
	szYes		db "Правильно!", 0
	szNo		db "Неверно. У треугольника на самом деле три угла.", 0
	szElse		db "У Вас возникли затруднения. Правильный ответ 'Да'.", 0
	szCapQuiz	db "Вопрос", 0
	szCapResult	db "Ответ", 0
	clsName		db "Static", 0
	sec		db " сек.", 0
	tmp		db 100 dup(0)
	fmt		db "%d", 0

.data?
	hInstance	HINSTANCE ?
	CommandLine	LPSTR ?
	hStatic		dd ?

.code
start:
	invoke	GetModuleHandle, NULL
	mov	hInstance, eax
	invoke	GetCommandLine
	mov	CommandLine, eax
	invoke	WinMain, hInstance, NULL, CommandLine, SW_SHOWDEFAULT
	invoke	ExitProcess, eax

WinMain proc hInst:HINSTANCE, hPrevInst:HINSTANCE, CmdLine:LPSTR, CmdShow:DWORD
LOCAL uiResult:DWORD
LOCAL hThread:DWORD
LOCAL buf[512]:BYTE

	; Создаю поток для отсчёта секунд на фоне показа окна сообщения
	mov edx, offset ThreadProc
	invoke CreateThread, NULL, 0, edx, 0, 0, NULL
	mov hThread, eax

	; Подготавливаем начальный текст заголовка окна сообщения с указанием полного кол-ва секунд
	invoke UpdateCaption, dwTimeOut, addr buf

	; Вызов пользовательской процедуры создания окна сообщения
	invoke TimedMessageBox, NULL, addr szQuestion, addr buf, MB_YESNO or MB_ICONQUESTION, dwTimeOut
	mov uiResult, eax
	; Обработка результата
	; В каждом случае сначала останавливаем и убиваем поток.
	; Окно сообщения можно и не показывать - дело вкуса.
	.if eax == IDYES
		invoke SuspendThread, hThread
		invoke CloseHandle, hThread
		invoke MessageBox, NULL, addr szYes, addr szCapResult, MB_OK or MB_ICONEXCLAMATION
		ret
	.elseif eax == IDNO
		invoke SuspendThread, hThread
		invoke CloseHandle, hThread
		invoke MessageBox, NULL, addr szNo, addr szCapResult, MB_OK or MB_ICONERROR
		ret
	.elseif eax == 0
		invoke SuspendThread, hThread
		invoke CloseHandle, hThread
		invoke MessageBox, NULL, addr szElse, addr szCapResult, MB_OK or MB_ICONINFORMATION
		ret
	.endif
	xor eax, eax
	ret
WinMain endp

; Пользовательская функция создания окна сообщения
TimedMessageBox proc hwndParent:DWORD, ptszMessage:DWORD, ptszTitle:DWORD, flags:DWORD, dwTimeout:DWORD
LOCAL idTimer:DWORD
LOCAL uiResult:DWORD
LOCAL msg:MSG

	; Установка таймера на указанное количество секунд
	invoke SetTimer, NULL, 0, dwTimeout, offset MessageBoxTimer
	mov idTimer, eax

	; Создание окна сообщения
	invoke MessageBox, hwndParent, [ptszMessage], [ptszTitle], flags
	mov uiResult, eax

	; Удаление таймера
	invoke KillTimer, NULL, idTimer

	; Выбор сообщения WM_QUIT из очереди с последующим его удалением
	invoke PeekMessage, addr msg, NULL, WM_QUIT, WM_QUIT, PM_REMOVE
	.if eax != 0	; Если достигнут таймаут, выходим
		xor eax, eax
		ret
	.endif

	xor eax, eax
	ret
TimedMessageBox endp

; Пользовательская функция закрытия окна сообщения и завершения приложения
MessageBoxTimer proc hwnd:DWORD, uiMsg:DWORD, idEvent:DWORD, dwTime:DWORD
	; Принудительное завершение приложения
	invoke PostQuitMessage, NULL
	ret
MessageBoxTimer endp

; Стандартная процедура потока
ThreadProc proc param:DWORD
LOCAL hMsg:DWORD
LOCAL buf[512]:BYTE

	push [param]
	pop hStatic
	.if hStatic == 0
		; Если хэндла окна сообщения еще нет, ищем его
		invoke EnumWindows, addr EnumWindowsProc, NULL
	.endif
	; Перестраиваем текста заголовка
	invoke UpdateCaption, dwTimeOut, addr buf
	; Переписываем текст заголовка окна сообщения
	invoke SetWindowText, hStatic, addr buf

	; Отнимаем от текущего количества секунд одну секунду
	sub dwTimeOut, 1000

	; Задержка на одну секунду
	invoke Sleep, 1000
	; Повторяем
	invoke ThreadProc, hStatic
	xor eax, eax
	ret
ThreadProc endp

; Процедура перечисления окон
EnumWindowsProc proc hwnd:HWND, lParam:DWORD
LOCAL buf[255]:BYTE

	; Перечисляем все окна в системе, отыскивая наше окно сообщения по его заголовку
	; Для простоты сравниваю только первые четыре символа текста заголовка
	invoke GetWindowText, hwnd, addr buf, 255
	.if dword ptr[buf] == 'рпоВ'
		mov eax, hwnd
		mov hStatic, eax
	.endif
	xor eax, eax
	inc eax
	ret
EnumWindowsProc endp

; Процедура построения текста окна сообщения 
; (основной текст + количество секунд, оставшихся до автоматического закрытия окна сообщения)
UpdateCaption proc uses esi edi edx ecx cnt:DWORD, buf:DWORD
	lea esi, offset szCapQuiz
	mov ecx, sizeof szCapQuiz
	dec ecx
	mov edi, [buf]
	rep movsb
	mov dword ptr[edi], 202D2020h
	add edi, 4
	mov eax, cnt
	mov ecx, 1000
	xor edx, edx
	div ecx
	invoke wsprintf, addr tmp, addr fmt, eax
	lea esi, offset tmp
	@@:
		lodsb
		or al, al
		jz @F
		stosb
		jmp @B
	@@:
	lea esi, offset sec
	mov ecx, sizeof sec
	rep movsb

	xor eax, eax
	ret
UpdateCaption endp

end start
